﻿#include "Sandbox2D.h"
#include "imgui.h"

#include "ImGuizmo.h"

static const uint32_t s_unMapWidth = 10;
static const char* s_cTownMap =
"...GGGG..."
"..GGGGGG.."
".GGGDDGGG."
".GGDGGDGG."
".GDGMMGDG."
".GDGMMGDG."
".GGDGGDGG."
".GGGDDGGG."
"..GGGGGG.."
"...GGGG..."
;

CSandbox2D::CSandbox2D() : 
	CLayer("Sandbox2D"), m_vec4Color({ 0.70, 0.87, 0.57, 1.0 }), m_cameraController(1280.0f / 720.0f, true), 
	m_fRotate(0.0f), m_mapTown(s_cTownMap, s_unMapWidth), m_bViewportFocused(false), m_bViewportHovered(false)
{
}

void CSandbox2D::OnAttach()
{
	m_pTexCheckboard = Hazal::CTexture2D::Create("../../Assets/Textures/chessboard.gif");
	m_pTownSpriteSheet = Hazal::CTexture2D::Create("../../Assets/Textures/tilemap_packed.png");
	m_pSubTexTown = Hazal::CSubTexture2D::CreateFromCoords(m_pTownSpriteSheet, 16, { 5.0f, 8.0f });

	/// Textures of scene toolbar
	m_pTexScenePlay = Hazal::CTexture2D::Create("../../Assets/Textures/Icons/Toolbar_PlayIcon.png");
	m_pTexSceneStop = Hazal::CTexture2D::Create("../../Assets/Textures/Icons/Toolbar_StopIcon.png");

	Hazal::SFrameBufferSpecification fbSpec;
	fbSpec.m_unWidth = 1280;
	fbSpec.m_unHeight = 720;
	fbSpec.m_attachments = {Hazal::EFramebufferTextureFormat::RGBA8, Hazal::EFramebufferTextureFormat::RED_INTEGER, Hazal::EFramebufferTextureFormat::Depth};

	/// Create frame buffer
	m_pFramebuffer = Hazal::CFrameBuffer::Create(fbSpec);

	/// Create scene
	m_pEditScene = Hazal::CreateRef<Hazal::CScene>();
	m_pActiveScene = m_pEditScene;

	m_panel.SetContext(m_pActiveScene);

	/// Initialize the editor camera
	m_editorCamera = Hazal::CEditorCamera(30.0f, 1.778f, 0.01f, 1000.0f);
}

void CSandbox2D::OnDetach()
{
}

void CSandbox2D::OnUpdate(float fTimeSpan)
{
	HZ_PROFILE_FUNCTION();

	/// Update scripts
	{
	}

	Hazal::SFrameBufferSpecification spec = m_pFramebuffer->GetSpecification();
	if (m_vecViewportPanelPos.x > 0.0f && m_vecViewportPanelPos.y > 0.0f &&
		(spec.m_unWidth != m_vecViewportPanelPos.x || spec.m_unHeight != m_vecViewportPanelPos.y))
	{
		m_pFramebuffer->Resize((uint32_t)m_vecViewportPanelPos.x, (uint32_t)m_vecViewportPanelPos.y);

		/// Re-caculate view projection matrix
		m_editorCamera.SetViewportSize(m_vecViewportPanelPos.x, m_vecViewportPanelPos.y);
		m_pActiveScene->OnViewportResize((uint32_t)m_vecViewportPanelPos.x, (uint32_t)m_vecViewportPanelPos.y);
	}

	/// Reset stat info
	Hazal::CRenderer2D::ResetStats();

	/// Render prepare
	{
		HZ_PROFILE_SCOPE("Renderer Prep")
		
		m_pFramebuffer->Bind(); /// Bind frambuffer, Off-screen Rendering
		Hazal::CRenderCommand::SetClearColor(m_vec4Color);
		Hazal::CRenderCommand::Clear();

		/// Clear entity id attachment of current framebuffer to -1
		m_pFramebuffer->ClearAttachment(1, -1);
	}
	
	{
		HZ_PROFILE_SCOPE("Renderer Draw")

		/// Update scene according scene state
		switch (m_eSceneState)
		{
		case ESceneState::Play:
		{
			m_pActiveScene->OnUpdateRuntime(fTimeSpan);
			break;
		}
		case ESceneState::Edit:
		{
			if (m_bViewportFocused)
			{
				/// Update editor camera
				m_editorCamera.OnUpdate(fTimeSpan);
			}
			m_pActiveScene->OnUpdateEditor(fTimeSpan, m_editorCamera);
		}
		}

		/// Calculate the relatively mouse position
		glm::vec2 vecViewportSize = m_aVecViewportBounds[1] - m_aVecViewportBounds[0];

		auto mousePos = ImGui::GetMousePos();

		mousePos.x -= m_aVecViewportBounds[0].x;
		mousePos.y -= m_aVecViewportBounds[0].y;
		mousePos.y = vecViewportSize.y - mousePos.y;

		int nMouseX = (int)mousePos.x;
		int nMouseY = (int)mousePos.y;

		if (nMouseX >= 0 && nMouseY >= 0 && nMouseX < (int)vecViewportSize.x && nMouseY < (int)vecViewportSize.y)
		{
			int nPixelData = m_pFramebuffer->ReadPixel(1, nMouseX, nMouseY);
			if (-1 == nPixelData)
			{
				/// 如果imguizmo未绘制, 才更新当前选中的entity
				if (!m_bImGuiZmoDrew)
				{
					m_enHoveredEntity = Hazal::CEntity();
				}
			}
			else
			{
				m_enHoveredEntity = Hazal::CEntity((entt::entity)nPixelData, m_pActiveScene.get());
			}
		}

		/// Save off screen rendering result
		m_pFramebuffer->Unbind();
	}
}

void CSandbox2D::OnEvent(Hazal::CEvent& event)
{
#if 0
	m_cameraController.OnEvent(event);
#else
	m_editorCamera.OnEvent(event);
#endif

	Hazal::CEventDispatcher dispatcher(event);
	dispatcher.Dispatch<Hazal::CKeyPressedEvent>(HZ_BIND_EVENT_FN(CSandbox2D::OnKeyPressedEvent));
	dispatcher.Dispatch<Hazal::CMouseButtonPressedEvent>(HZ_BIND_EVENT_FN(CSandbox2D::OnMouseButtonPressedEvent));
}

void CSandbox2D::OnImGuiRender()
{
	HZ_PROFILE_FUNCTION()

    static bool bDockspaceOpen = true;
    static bool opt_fullscreen = true;
    static bool opt_padding = false;
    static ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_None;

    // We are using the ImGuiWindowFlags_NoDocking flag to make the parent window not dockable into,
    // because it would be confusing to have two docking targets within each others.
    ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking;
    if (opt_fullscreen)
    {
        const ImGuiViewport* viewport = ImGui::GetMainViewport();
        ImGui::SetNextWindowPos(viewport->WorkPos);
        ImGui::SetNextWindowSize(viewport->WorkSize);
        ImGui::SetNextWindowViewport(viewport->ID);
        ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.5f);
        ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
        window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
        window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
    }
    else
    {
        dockspace_flags &= ~ImGuiDockNodeFlags_PassthruCentralNode;
    }

    // When using ImGuiDockNodeFlags_PassthruCentralNode, DockSpace() will render our background
    // and handle the pass-thru hole, so we ask Begin() to not render a background.
    if (dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode)
        window_flags |= ImGuiWindowFlags_NoBackground;

    // Important: note that we proceed even if Begin() returns false (aka window is collapsed).
    // This is because we want to keep our DockSpace() active. If a DockSpace() is inactive,
    // all active windows docked into it will lose their parent and become undocked.
    // We cannot preserve the docking relationship between an active window and an inactive docking, otherwise
    // any change of dockspace/settings would lead to windows being stuck in limbo and never being visible.
    if (!opt_padding)
        ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
    
	/// Dockspace Begin
	ImGui::Begin("DockSpace", &bDockspaceOpen, window_flags);
    if (!opt_padding)
        ImGui::PopStyleVar();

    if (opt_fullscreen)
        ImGui::PopStyleVar(2);

    // Submit the DockSpace
    ImGuiIO& io = ImGui::GetIO();
	ImGuiStyle& style = ImGui::GetStyle();

	float fWinMinSizeX = style.WindowMinSize.x;
	style.WindowMinSize.x = 370.0f;

	if (io.ConfigFlags & ImGuiConfigFlags_DockingEnable)
    {
        ImGuiID dockspace_id = ImGui::GetID("MyDockSpace");
        ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags);
    }

	style.WindowMinSize.x = fWinMinSizeX;

    if (ImGui::BeginMenuBar())
    {
        if (ImGui::BeginMenu("Options"))
        {
            // Disabling fullscreen would allow the window to be moved to the front of other windows,
            // which we can't undo at the moment without finer window depth/z control.
            ImGui::MenuItem("Full Screen", NULL, &opt_fullscreen);
            ImGui::MenuItem("Padding", NULL, &opt_padding);
            ImGui::Separator();

			if (ImGui::MenuItem("New", "Ctrl+N"))
			{
				NewScene();
			}

			if (ImGui::MenuItem("Open...", "Ctrl+O"))
			{
				OpenScene();
			}

			if (ImGui::MenuItem("Save As...", "Ctrl+Shift+S"))
			{
				SaveSceneAs();
			}

            if (ImGui::MenuItem("Exit"))
			{
				/// Close Main window loop
				Hazal::CApplication::Instance().Close();
			}
            ImGui::Separator();

            ImGui::EndMenu();
        }
        ImGui::EndMenuBar();
    }

	/// User Imgui window
	m_panel.OnImGuiRender();
	m_contBrowerPanel.OnImGuiRender();

	if (ImGui::Begin("Settings"))
	{

		auto stats = Hazal::CRenderer2D::GetStats();
		if (!m_enHoveredEntity)
		{
			ImGui::Text("Selected entity: %s", "None");
		}
		else
		{
			ImGui::Text("Selected entity: %s", m_enHoveredEntity.GetComponent<Hazal::STagComponent>().m_strTag.c_str());
		}
		ImGui::Text("Renderer2D stats");
		ImGui::Text("Draw calls :    %d", stats.m_unDrawCall);
		ImGui::Text("Quad count :    %d", stats.m_unQuadCount);
		ImGui::Text("Quad vertices : %d", stats.GetTotalVertexCount());
		ImGui::Text("Quad indices  : %d", stats.GetTotalIndexCount());

		ImGui::ColorEdit4("Background Color", glm::value_ptr(m_vec4Color));

		ImGui::End();
	}

	/// Available space from current position
	ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2{ 0, 0 });	/// 添加框体内边距
	if (ImGui::Begin("Viewports"))
	{
		/// 获取当前窗体内的光标基础位置(不随鼠标移动发生改变, 包含了窗体的tab bar的偏移)
		auto viewportOffset = ImGui::GetCursorPos();

		/// Set ImGui events block state based on "viewports" window focus state 
		m_bViewportFocused = ImGui::IsWindowFocused();
		m_bViewportHovered = ImGui::IsWindowHovered();
		Hazal::CApplication::Instance().GetImGuiLayer()->SetBlockEvents(!m_bViewportFocused && !m_bViewportHovered);
		m_pActiveScene->SetViewportFocused(m_bViewportFocused);

		ImVec2 viewportPanelSize = ImGui::GetContentRegionAvail();
		if (m_vecViewportPanelPos != *((glm::vec2*)(&viewportPanelSize)) && viewportPanelSize.x > 0.0f && viewportPanelSize.y > 0.0f)
		{
			m_vecViewportPanelPos = { viewportPanelSize.x, viewportPanelSize.y };
		}

		uint32_t unTextureID = m_pFramebuffer->GetColorAttachmentRendererID();
		ImGui::Image(unTextureID, viewportPanelSize, { 0.0f, 1.0f }, { 1.0f, 0.0f });

		/// 获取拖动到Viewport界面的.hazal文件路径
		if (ImGui::BeginDragDropTarget())
		{
			const ImGuiPayload* pPayload = ImGui::AcceptDragDropPayload("CONTENT_BROWSER_ITEM");
			/*	
				Warning:
				在松开默认的鼠标左键前, BeginDragDropTarget = true, 但此时左键未松开, 一次delivery尚未完成.
				这导致接收到的pPayload = nullptr, 如果不判空指针一定会出问题, 所以这里必须进行非空判断.
			*/
			if (nullptr != pPayload)
			{
				const wchar_t* cPath = (const wchar_t*)pPayload->Data;
				OpenScene(cPath);
			}

			ImGui::EndDragDropTarget();
		}

		/// 计算viewport的绝对位置
		auto windowSize = ImGui::GetWindowSize();
		windowSize.y -= viewportOffset.y;	/// tab bar未隐藏时, 要减去tab bar的高度

		ImVec2 vecMinBound = ImGui::GetWindowPos();
		vecMinBound.x += viewportOffset.x;
		vecMinBound.y += viewportOffset.y;

		ImVec2 vecMaxBound = { vecMinBound.x + windowSize.x, vecMinBound.y + windowSize.y };

		m_aVecViewportBounds[0] = { vecMinBound.x, vecMinBound.y };
		m_aVecViewportBounds[1] = { vecMaxBound.x, vecMaxBound.y };

		/// ImGuizmo state. If current state is in play, stop edit.
		if (ESceneState::Play == m_eSceneState)
		{
			m_nGizmoType = -1;
		}

		m_bImGuiZmoDrew = false;

		Hazal::CEntity selectedEntity = m_panel.GetSelectedEntity();
		if (selectedEntity && m_nGizmoType != -1)
		{
			IMGUIZMO_NAMESPACE::SetOrthographic(false);
			/// Draw current window
			IMGUIZMO_NAMESPACE::SetDrawlist();

			/// Draw current viewport
			IMGUIZMO_NAMESPACE::SetRect(ImGui::GetWindowPos().x, ImGui::GetWindowPos().y, m_vecViewportPanelPos.x, m_vecViewportPanelPos.y);

			m_bImGuiZmoDrew = true;

#if 0
			/// Get primary camera(runtime camera)
			auto primaryCamera = m_pScene->GetPrimaryCamera();
			const auto& camera = primaryCamera.GetComponent<Hazal::SCameraComponent>().m_Camera;
			glm::mat4 matCameraView = glm::inverse(primaryCamera.GetComponent<Hazal::STransformComponent>().GetTransform());
			const glm::mat4& matCameraProjection = camera.GetProjection();
#else
			/// Get editor camera

			const glm::mat4& matCameraView = m_editorCamera.GetViewMatrix();
			const glm::mat4 matCameraProjection = m_editorCamera.GetProjection();
#endif

			/// Selected entity transform
			auto& tc = selectedEntity.GetComponent<Hazal::STransformComponent>();
			glm::mat4 matSelectedTransform = tc.GetTransform();

			/// Snapping
			bool bSnap = Hazal::CInput::IsKeyPressed(HZ_KEY_LEFT_CONTROL);
			float fSnapValue = 0.5f;
			if (IMGUIZMO_NAMESPACE::OPERATION::ROTATE == m_nGizmoType)
			{
				fSnapValue = 45.0f;
			}

			float aSnapValues[3] = { fSnapValue, fSnapValue, fSnapValue };

			/// ImGuizmo Manipulate
			IMGUIZMO_NAMESPACE::Manipulate(glm::value_ptr(matCameraView), glm::value_ptr(matCameraProjection),
				(IMGUIZMO_NAMESPACE::OPERATION)m_nGizmoType, IMGUIZMO_NAMESPACE::MODE::LOCAL, glm::value_ptr(matSelectedTransform), 
				nullptr, bSnap ? aSnapValues : nullptr);

			if (IMGUIZMO_NAMESPACE::IsUsing())
			{
				glm::vec3 vecTranslation, vecRotation, vecScale;
				Hazal::Math::DecomposeTransform(matSelectedTransform, vecTranslation, vecRotation, vecScale);

				tc.m_vecTranslation = vecTranslation;
				tc.m_vecRotation = vecRotation;
				tc.m_vecScale = vecScale;
			}

		}

		ImGui::End();
	}
	ImGui::PopStyleVar();

	///UI tool bar
	UI_Toolbar();

	/// Dock space End
    ImGui::End();
}

void CSandbox2D::UI_Toolbar()
{
	ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 0.0f, 2.0f });
	ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, { 0.0f, 0.0f });
	ImGui::PushStyleColor(ImGuiCol_Button, { 0.0f, 0.0f, 0.0f, 0.0f });

	auto& color = ImGui::GetStyle().Colors;
	auto& colBtnHovered = color[ImGuiCol_ButtonHovered];
	auto& colBtnActive = color[ImGuiCol_ButtonActive];
	ImGui::PushStyleColor(ImGuiCol_ButtonHovered, { colBtnHovered.x, colBtnHovered.y, colBtnHovered.z, 0.5f });
	ImGui::PushStyleColor(ImGuiCol_ButtonActive, { colBtnActive.x, colBtnActive.y, colBtnActive.z, 0.5f });

	ImGui::Begin("##toolbar", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);

	Hazal::Ref<Hazal::CTexture2D> pIcon = (ESceneState::Edit == m_eSceneState) ? m_pTexScenePlay : m_pTexSceneStop;
	float fSize = ImGui::GetWindowHeight() - 8.0f;

	/// ImGui::SameLine((ImGui::GetWindowContentRegionMax().x * 0.5f) - fSize * 0.5f);
	ImGui::SetCursorPosX((ImGui::GetWindowContentRegionMax().x * 0.5f) - fSize * 0.5f);
	if (ImGui::ImageButton("Toolbar", (ImTextureID)pIcon->GetRendererID(), {fSize, fSize},  { 0.0f, 1.0f }, { 1.0f, 0.0f }))
	{
		if (ESceneState::Edit == m_eSceneState)
		{
			OnScenePlay();
		}
		else if (ESceneState::Play == m_eSceneState)
		{ 
			OnSceneEdit();
		}
		else {}
	}
	ImGui::End();

	ImGui::PopStyleColor(3);
	ImGui::PopStyleVar(2);
}

void CSandbox2D::OnSceneEdit()
{
	m_eSceneState = ESceneState::Edit;
	
	m_pActiveScene = m_pEditScene;
	m_pActiveScene->OnRunTimeStop();
}

void CSandbox2D::OnScenePlay()
{
	m_eSceneState = ESceneState::Play;

	m_pActiveScene = Hazal::CScene::Copy(m_pEditScene);
	m_pActiveScene->OnRunTimeStart();
}

bool CSandbox2D::OnKeyPressedEvent(Hazal::CKeyPressedEvent& event)
{
	if (event.GetRepeatCount() > 0)
	{
		return false;
	}

	bool bCtrlPressed = Hazal::CInput::IsKeyPressed(HZ_KEY_LEFT_CONTROL) || Hazal::CInput::IsKeyPressed(HZ_KEY_RIGHT_CONTROL);
	bool bShiftPressed = Hazal::CInput::IsKeyPressed(HZ_KEY_LEFT_SHIFT) || Hazal::CInput::IsKeyPressed(HZ_KEY_RIGHT_SHIFT);

	switch (event.GetKeyCode())
	{
	/// 场景加载相关
	case HZ_KEY_S:
	{
		if (bCtrlPressed)
		{
			if(bShiftPressed)
			{
				SaveSceneAs();
			}
			else
			{
				SaveScene();
			}
		}
		break;
	}
	case HZ_KEY_N:
	{
		if (bCtrlPressed)
		{
			NewScene();
		}
		break;
	}
	case HZ_KEY_O:
	{
		if (bCtrlPressed)
		{
			OpenScene();
		}
		break;
	}

	/// Scene commands
	case HZ_KEY_D:
	{
		if (bCtrlPressed)
		{
			OnDuplicateEntitty();
		}
		break;
	}

	/// ImGuiZmo 相关
	case HZ_KEY_Q:
	{
		m_nGizmoType = -1;
		break;
	}
	case HZ_KEY_W:
	{
		m_nGizmoType = IMGUIZMO_NAMESPACE::OPERATION::TRANSLATE;
		break;
	}
	case HZ_KEY_E:
	{
		m_nGizmoType = IMGUIZMO_NAMESPACE::OPERATION::ROTATE;
		break;
	}
	case HZ_KEY_R:
	{
		m_nGizmoType = IMGUIZMO_NAMESPACE::OPERATION::SCALE;
		break;
	}
	default:
		break;
	}
	return false;
}

bool CSandbox2D::OnMouseButtonPressedEvent(Hazal::CMouseButtonPressedEvent& event)
{
	if (HZ_MOUSE_BUTTON_LEFT == event.GetMouseButton())
	{
		/// 鼠标悬停在ImGuiZmo绘制的控制框内时, ImGuiZmo::::IsOver()永远返回false, 此时即使获取像素对应的entity已经发生变化, 也不会更新entity
		if (m_bViewportHovered && !IMGUIZMO_NAMESPACE::IsOver() && !Hazal::CInput::IsKeyPressed(HZ_KEY_LEFT_ALT))
		{
			m_panel.SetSelectedEntity(m_enHoveredEntity);
		}
	}

	return false;
}

void CSandbox2D::NewScene()
{
	m_pActiveScene = Hazal::CreateRef<Hazal::CScene>();
	m_pActiveScene->OnViewportResize((uint32_t)m_vecViewportPanelPos.x, (uint32_t)m_vecViewportPanelPos.y);
	m_panel.SetContext(m_pActiveScene);

	m_editScenePath = std::filesystem::path();
}

void CSandbox2D::OpenScene()
{
	std::string strFilePath = Hazal::CFileDialogs::OpenFile("Hazal Scene (*.hazal)\0*.hazal\0");

	if (!strFilePath.empty())
	{
		OpenScene(strFilePath);
	}
}

void CSandbox2D::OpenScene(const std::filesystem::path& rPath)
{
	if (ESceneState::Edit != m_eSceneState)
	{
		m_pActiveScene->OnRunTimeStop();
	}

	if (rPath.extension() != ".hazal")
	{
		LOG_ERROR("Wrong path extension: {0}, expected .hazal", rPath.extension().filename().string());
		return;
	}

	m_pEditScene = Hazal::CreateRef<Hazal::CScene>();
	m_pEditScene->OnViewportResize((uint32_t)m_vecViewportPanelPos.x, (uint32_t)m_vecViewportPanelPos.y);

	m_pActiveScene = m_pEditScene;
	m_panel.SetContext(m_pActiveScene);

	Hazal::CSceneSerializer serializer(m_pEditScene);
	serializer.Deserialize(rPath.string());

	m_editScenePath = rPath;
}

void CSandbox2D::OnDuplicateEntitty()
{
	if (ESceneState::Edit != m_eSceneState)
	{
		return;
	}

	Hazal::CEntity selectedEntity = m_panel.GetSelectedEntity();
	m_pEditScene->DuplicateEntity(selectedEntity);
}

void CSandbox2D::SaveSceneAs()
{
	std::string strFilePath = Hazal::CFileDialogs::SaveFile("Hazal Scene (*.hazal)\0*.hazal\0");

	if (!strFilePath.empty())
	{
		Hazal::CSceneSerializer serializer(m_pActiveScene);
		serializer.Serialize(strFilePath);

		m_editScenePath = strFilePath;
	}

}

void CSandbox2D::SaveScene()
{
	if (!m_editScenePath.empty())
	{
		Hazal::CSceneSerializer serializer(m_pActiveScene);
		serializer.Serialize(m_editScenePath.string());
	}
	else
	{
		SaveSceneAs();
	}
}
